/*
 * Copyright (C) 2011, 2012 Moravská zemská knihovna v Brně / Moravian Library in Brno
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package cz.mzk.oai.provider.response;

import cz.mzk.oai.metadataRepository.metadata.DateStamp;
import cz.mzk.oai.metadataRepository.metadata.MetadataFormat;
import cz.mzk.oai.metadataRepository.repository.Repository;
import cz.mzk.oai.metadataRepository.repository.records.Record;
import cz.mzk.oai.provider.ErrorCode;
import cz.mzk.oai.provider.OaiException;
import cz.mzk.oai.provider.response.listRequests.CompleteList;
import cz.mzk.oai.provider.response.listRequests.RequestSequence;
import cz.mzk.oai.provider.response.listRequests.ResumptionTokenManager;
import cz.mzk.oai.provider.tools.ElementAppender;
import cz.mzk.oai.provider.tools.Parser;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.joda.time.DateTime;

/**
 *
 * @author Martin Řehánek (rehan at mzk.cz)
 */
public abstract class AbstractListResponse extends OaiVerbResponse {

    private static final Logger logger = Logger.getLogger(AbstractListResponse.class.getName());
    private final String FROM = "from";
    private final String UNTIL = "until";
    private final String METADATA_PREFIX = "metadataPrefix";
    private final String SET = "set";
    private final String RESUMPTION_TOKEN = "resumptionToken";

    public AbstractListResponse(String verbStr, Map<String, String[]> parameters, String baseUrl) throws IOException {
        super(verbStr, parameters, baseUrl);
    }

    @Override
    String[] getRequiredArguments() {
        String[] result = {METADATA_PREFIX};
        return result;
    }

    @Override
    String[] getOptionalArguments() {
        String[] result = {FROM, UNTIL, SET};
        return result;
    }

    @Override
    String getExclusiveArgument() {
        return RESUMPTION_TOKEN;
    }

    @Override
    void createResponse() throws OaiException, IOException {
        String resumptionToken = getArgumentValueIfPresent(RESUMPTION_TOKEN);
        if (resumptionToken == null) {
            logger.log(Level.FINE, "first request sequence");
            createResponseForFirstRecords();
        } else {
            logger.log(Level.FINE, "response from resumptionToken {0}", resumptionToken);
            createResponseFromToken(resumptionToken);
        }
    }

    private void createResponseForFirstRecords() throws OaiException, IOException {
        String metadataPrefix = getArgumentValueIfPresent(METADATA_PREFIX);
        MetadataFormat format = Parser.parseMetadataPrefix(metadataPrefix);
        ListConditions conditions = getListConditions();
        logger.log(Level.FINE, "getting records from repository");
        Set<Record> records = getRecords(format, conditions);
        logger.log(Level.FINE, "building ListRequest");
        CompleteList request = new CompleteList(records);
        logger.log(Level.FINE, "creating RequestSequence");
        RequestSequence part = new RequestSequence(request, 0, getResultPartsManager().returnedRecords());
        //logger.log(Level.INFO, "creating new resumptionToken");
        String resumptionToken = getResultPartsManager().registerNextResultPart(part);
        //logger.log(Level.INFO, "resumptionToken: {0}", resumptionToken);
        createResponse(part, resumptionToken);

    }

    private ListConditions getListConditions() throws OaiException {
        String set = getArgumentValueIfPresent(SET);
        String from = getArgumentValueIfPresent(FROM);
        String until = getArgumentValueIfPresent(UNTIL);
        return ListConditions.instanceOf(set, from, until);
    }

    private Set<Record> getRecords(MetadataFormat format, ListConditions conditions) throws OaiException {
        String setSpec = conditions.getSetSpec();
        DateStamp from = conditions.getFrom();
        DateStamp until = conditions.getUntil();
        Set<Record> records = getRecords(format, setSpec, from, until);
        if (records.isEmpty()) {
            throw new OaiException(ErrorCode.noRecordsMatch,
                    " no records for format: " + format.toString()
                    + ", setSpec: " + setSpec
                    + ", from: " + from
                    + ", until: " + until);
        }
        return records;
    }

    private Set<Record> getRecords(MetadataFormat format, String setSpec, DateStamp from, DateStamp until) {
        Repository repository = config.getRepository();
        if (setSpec == null) {
            return repository.getRecords(format, from, until);
        } else {
            return repository.getRecords(format, setSpec, from, until);
        }
    }

    private void createResponseFromToken(String resumptionToken) throws OaiException, IOException {
        RequestSequence part = getResultPartsManager().requestSeqFromResToken(resumptionToken);
        if (part == null) {
            throw new OaiException(ErrorCode.badResumptionToken, "invalid or expired resumption token");
        }
        String nextResumptionToken = getResultPartsManager().registerNextResultPart(part);
        createResponse(part, nextResumptionToken);
    }

    private void createResponse(RequestSequence part, String resumptionToken) throws IOException {
        for (Record record : part.getRecords()) {
            appendRecordDataToRoot(record);
        }
        int completeSize = part.getRequest().totalSize();
        int cursor = part.cursor();
        DateTime validUntil = part.getValidUntil();
        ElementAppender.appendResumptionToken(rootEl, completeSize, cursor, validUntil, resumptionToken);
    }

    abstract ResumptionTokenManager getResultPartsManager();

    abstract void appendRecordDataToRoot(Record record) throws IOException;
}
